package scanning;

import java.awt.Button;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Panel;
import java.awt.Point;
import java.awt.TextField;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.HashSet;

import simulator.Log;

public class ScanningKeyboard extends Frame {
	private static final long serialVersionUID = 1L;
	
	private static final int textFieldWidth = 100;
	private static final int ambiguousWordNum = 4;
	private static final int windowWidth = 1000;
	private static final int windowHeight = 200;
	private static final int wordButtonWidth = 150;
	private static final int wordButtonHeight = 25;
	private static final int pageButtonWidth = 25;
	private static final int pageButtonHeight = 25;
	private static final int letterButtonWidth = 25;
	private static final int letterButtonHeight = 25;
	
	private State state;
	private ArrayList<Integer> groupSize;
	private String phraseText;
	private String inputText;
	private int wordPage;
	private ArrayList<String> words;
	
	private TextField phraseField;
	private TextField inputField;
	private Panel wordPanel;
	private Button lastPageButton;
	private Button nextPageButton;
	private Button[] wordButton;
	private Panel letterPanel;
	private Button confirmButton;
	private Panel[] letterGroup;
	private Button[][] letterButton;
	private Button deleteButton;
	
	private ScanningSimulator listener;
	private Log log;
	
	private enum ButtonName {
		CONFIRM,
		LETTER,
		DELETE,
		WORD,
		LAST_PAGE,
		NEXT_PAGE
	};
	
	private class State {
		/**
		 * The State of a DFA to shift the highlight button.
		 * DFA: (a) for accept, (r) for reject
		 * +---------------------------------------------------------------------------------------+
		 * |CONFIRM --> LETTER(Group 0) --> ... --> LETTER(Group (loop - 1)) --> DELETE --> CONFIRM|
		 * |   |    (r)                 (r)     (r)                          (r)        (r)        |
		 * |   |(a)                                                                                |
		 * |   V          (r)     (r)                        (r)           (r)           (r)       |
		 * |WORD(Group 0) --> ... --> WORD(Group (loop - 1)) --> NEXT_PAGE --> LAST_PAGE --> WORD  |
		 * +---------------------------------------------------------------------------------------+
		 * Otherwise (a) point to END, triggering function pressXxxButton().
		 */
		ButtonName buttonName;
		int arg;
		
		private State() {
			buttonName = ButtonName.CONFIRM;
			arg = 0;
		}
		
		private Color checkColor(ButtonName buttonName, int arg) {
			/**
			 * Check if the state is associated with the button with args above and return an appropriate color.
			 */
			if (this.buttonName.equals(buttonName) && this.arg == arg) {
				return Color.YELLOW;
			}
			return Color.WHITE;
		}
		
		private void accept() {
			if (buttonName == ButtonName.CONFIRM) {
				pressConfirmButton();
			} else if (buttonName == ButtonName.LETTER) {
				pressLetterButton(arg);
			} else if (buttonName == ButtonName.DELETE) {
				pressDeleteButton();
			} else if (buttonName == ButtonName.LAST_PAGE) {
				pressLastPageButton();
			} else if (buttonName == ButtonName.WORD) {
				pressWordButton(arg);
			} else if (buttonName == ButtonName.NEXT_PAGE) {
				pressNextPageButton();
			} else {
				System.out.println("State error in ScanningKeyboard::State::accept()");
			}
		}
		
		private void reject() {
			if (buttonName == ButtonName.CONFIRM) {
				buttonName = ButtonName.LETTER;
				arg = 0;
				drawLetterPanel();
			} else if (buttonName == ButtonName.LETTER) {
				if (arg + 1 < groupSize.size()) {
					arg++;
				} else {
					buttonName = ButtonName.DELETE;
					arg = 0;
				}
				drawLetterPanel();
			} else if (buttonName == ButtonName.DELETE) {
				buttonName = ButtonName.CONFIRM;
				arg = 0;
				drawLetterPanel();
			} else if (buttonName == ButtonName.LAST_PAGE) {
				buttonName = ButtonName.WORD;
				arg = 0;
				drawWordPanel();
			} else if (buttonName == ButtonName.WORD) {
				int loop = ambiguousWordNum;
				if ((wordPage + 1) * ambiguousWordNum > words.size()) {
					loop = words.size() - wordPage * ambiguousWordNum;
				}
				if (arg + 1 < loop) {
					arg++;
				} else {
					buttonName = ButtonName.NEXT_PAGE;
					arg = 0;
				}
				drawWordPanel();
			} else if (buttonName == ButtonName.NEXT_PAGE) {
				buttonName = ButtonName.LAST_PAGE;
				arg = 0;
				drawWordPanel();
			} else {
				System.out.println("State error in ScanningKeyboard::State::accept()");
			}
		}
		
		private void pressConfirmButton() {
			if (words.size() > 0) {
				log.log("enter");
				buttonName = ButtonName.WORD;
				arg = 0;
				drawWordPanel();
				drawLetterPanel();
			}
		}
		
		private void pressLetterButton(int arg) {
			HashSet<Character> group = new HashSet<>();
			if (arg < groupSize.size()) {
				log.log("click " + (arg + 1));
				int beginList = 0;
				for (int i = 0; i < arg; i++) {
					beginList += groupSize.get(i);
				}
				for (int i = beginList; i < beginList + groupSize.get(arg); i++) {
					group.add((char)('a' + i));
				}
			}
			
			listener.userSelectGroup(group);
		}
		
		private void pressDeleteButton() {
			listener.userDelete();
		}
		
		private void pressLastPageButton() {
			if (wordPage - 1 >= 0) {
				log.log("lastPage");
				wordPage--;
			}
			drawWordPanel();
		}
		
		private void pressWordButton(int arg) {
			int id = wordPage * ambiguousWordNum + arg;
			if (id < words.size()) {
				listener.userSelectWord(id);
			}
		}
		
		private void pressNextPageButton() {
			if ((wordPage + 1) * ambiguousWordNum <= words.size() + (ambiguousWordNum - 1)) {
				log.log("nextPage");
				wordPage++;
			}
			drawWordPanel();
		}
	};
	
	public ScanningKeyboard(Log log) {
		this.log = log;
		
		setLayout(new FlowLayout());
		setTitle("Scanning Keyboard");
		setSize(windowWidth, windowHeight);
		setLocation(new Point(500, 400));
		
		//Listener for closing the frame.
		addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent event) {
				System.exit(0);
			}
		});
		
		setVisible(true);
	}
	
	public void setListener(ScanningSimulator listener) {
		this.listener = listener;
	}
	
	public void reset(ArrayList<Integer> groupSize, String phrase, String input, ArrayList<String> words) {
		/**
		 * Call it when the dictionary change:
		 * 1. The user select a word;
		 * 2. The user delete a word;
		 * 3. The user press a group of letters;
		 */
		state = new State();
		state.buttonName = ButtonName.CONFIRM;
		state.arg = 0;
		wordPage = 0;
		
		this.groupSize = groupSize;
		this.phraseText = phrase;
		this.inputText = input;
		this.words = words;
		
		draw();
	}
	
	public void accept() {
		/**
		 * Call it when the user press the button.
		 */
		state.accept();
	}
	
	public void reject() {
		/**
		 * Call it when the user don't press the button during a scanning period.
		 */
		state.reject();
	}
	
	private void draw() {
		/**
		 * Draw the keyboard according to the latest information: state and groupSize.
		 * +---------------+
		 * | Phrase        |
		 * | Input         |
		 * +-+--+--+--+--+-+
		 * |<|w1|w2|w3|w4|>|
		 * +-+--+--+--+--+-+
		 * |#|abc......wxyz|
		 * +-+-------------+
		 */
		drawTextField();
		drawWordPanel();
		drawLetterPanel();
	}
	
	private void drawTextField() {
		/**
		 * Text field include the phrase to input and user's input.
		 */
		if (phraseField == null) {
			phraseField = new TextField(textFieldWidth);
			phraseField.setEditable(false);
			add(phraseField);
		}
		if (phraseField.getText() != phraseText) {
			phraseField.setText(phraseText);
		}
		
		if (inputField == null) {
			inputField = new TextField(textFieldWidth);
			inputField.setEditable(false);
			inputField.setBackground(Color.WHITE);
			add(inputField);
		}
		if (inputField.getText() != inputText) {
			inputField.setText(inputText);
		}
		setVisible(true);
	}
	
	private void drawWordPanel() {
		/**
		 * Word Panel include several possible words (default = 4),
		 * button for last page and next page.
		 */
		if (wordPanel == null) {
			wordPanel = new Panel();
			wordPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 0));
			
			lastPageButton = new Button("<");
			lastPageButton.setPreferredSize(new Dimension(pageButtonWidth, pageButtonHeight));
			wordPanel.add(lastPageButton);
			
			wordButton = new Button[ambiguousWordNum];
			for (int i = 0; i < ambiguousWordNum; i++) {
				wordButton[i] = new Button();
				wordButton[i].setPreferredSize(new Dimension(wordButtonWidth, wordButtonHeight));
				wordPanel.add(wordButton[i]);
			}
			
			nextPageButton = new Button(">");
			nextPageButton.setPreferredSize(new Dimension(pageButtonWidth, pageButtonHeight));
			wordPanel.add(nextPageButton);
			
			add(wordPanel);
		}
		
		lastPageButton.setBackground(state.checkColor(ButtonName.LAST_PAGE, 0));
		for (int i = 0; i < ambiguousWordNum; i++) {
			wordButton[i].setBackground(state.checkColor(ButtonName.WORD, i));
			if (wordPage * ambiguousWordNum + i < words.size()) {
				wordButton[i].setLabel(words.get(wordPage * ambiguousWordNum + i));
			} else {
				wordButton[i].setLabel("");
			}
		}
		nextPageButton.setBackground(state.checkColor(ButtonName.NEXT_PAGE, 0));
		
		setVisible(true);
	}
	
	private void drawLetterPanel() {
		/**
		 * Letter Panel include a confirm button and some letter buttons.
		 */
		boolean shapeChanged = false;
		
		if (letterPanel == null) {
			shapeChanged = true;
		} else if (letterGroup.length != groupSize.size()) {
			shapeChanged = true;
		} else {
			for (int i = 0; i < groupSize.size(); i++) {
				if (letterButton[i].length != groupSize.get(i)) {
					shapeChanged = true;
				}
			}
		}
		
		if (shapeChanged) {
			if (letterPanel != null) {
				remove(letterPanel);
				letterPanel = null;
			}
				
			letterPanel = new Panel();
			letterPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 0));
			
			confirmButton = new Button("#");
			confirmButton.setPreferredSize(new Dimension(letterButtonWidth, letterButtonHeight));
			letterPanel.add(confirmButton);
			
			int beginLetter = 0;
			letterGroup = new Panel[groupSize.size()];
			letterButton = new Button[groupSize.size()][];
			for (int i = 0; i < groupSize.size(); i++) {
				int size = groupSize.get(i);
				
				letterGroup[i] = new Panel();
				letterGroup[i].setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
				letterGroup[i].setPreferredSize(new Dimension(letterButtonWidth * size, letterButtonHeight));
				letterButton[i] = new Button[size];
				for (int j = 0; j < size; j++) {
					letterButton[i][j] = new Button("" + (char)('A' + (beginLetter + j)));  
					letterButton[i][j].setPreferredSize(new Dimension(letterButtonWidth, letterButtonHeight));
					letterGroup[i].add(letterButton[i][j]);
				}
				letterPanel.add(letterGroup[i]);
	
				beginLetter += size;
			}
			
			deleteButton = new Button("<");
			deleteButton.setPreferredSize(new Dimension(letterButtonWidth, letterButtonHeight));
			letterPanel.add(deleteButton);
			
			add(letterPanel);
		}
		
		confirmButton.setBackground(state.checkColor(ButtonName.CONFIRM, 0));
		for (int i = 0; i < groupSize.size(); i++) {
			for (int j = 0; j < groupSize.get(i); j++) {
				letterButton[i][j].setBackground(state.checkColor(ButtonName.LETTER, i));
			}
		}
		deleteButton.setBackground(state.checkColor(ButtonName.DELETE, 0));
		
		setVisible(true);
	}
}
